/*
 *  This file is part of Bracket Properties
 *  Copyright 2011-2013 David R. Smith
 *  All Rights Reserved
 *
 */
package asia.redact.bracket.properties.serializer;

import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.iharder.Base64;

import org.apache.log4j.Logger;

import asia.redact.bracket.properties.Properties;
import asia.redact.bracket.util.SerialIDGenerator;

/**
 * Given the information on the render method calls, cause properties to be written which represent the objects in the graph.
 * 
 * @author Dave
 *
 */
public class PropertiesRenderer implements Renderer {
	
	final static Logger log = Logger.getLogger(PropertiesRenderer.class);
	final Properties props;
	final PackageMap packages;

	public PropertiesRenderer() {
		props = Properties.Factory.getInstance();
		packages = new PackageMap();
	}
	
	public PropertiesRenderer(Properties props) {
		this.props=props;
		packages = new PackageMap();
	}
	
	private String bcnKey(Class<?> cls){
		int val = packages.addPackage(cls);
		if(val == -1) return cls.getName();
		StringBuffer buf = new StringBuffer("$");
		buf.append(val);
		buf.append(".");
		buf.append(cls.getSimpleName());
		return buf.toString();
		
	}
	
	public void renderInitialKeyId(Token root){
		props.put(Renderer.INITIAL_KEY_NAME, String.valueOf(root.getId()));
	}
	
	public void renderLastKeyId(long last){
		props.put(Renderer.NEXT_KEY_NAME, String.valueOf(last));
	}
	
	@Override
	public void renderLoadable(Token token) {
		StringBuffer key = new StringBuffer(String.valueOf(token.getId()));
		key.append(".");
		key.append("fromString");
		String value = token.getObject().toString();
		props.put(key.toString(), value);
	}

	public void renderImmediateMapValue(Token parent, Token child, int ct){
	
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		key.append(".");
		key.append("v");
		String value = String.valueOf(child.getObject());
		props.put(key.toString(), value);
		
	}
	
	public void renderIndirectMapValue(Token parent, Token child, int ct){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		key.append(".");
		key.append("v");
		String value = "$"+String.valueOf(child.getId());
		props.put(key.toString(), value);
	}
	
	public void renderImmediate(Token parent, Token child){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(child.getKey());
		String value = String.valueOf(child.getObject());
		props.put(key.toString(), value);
	}
	
	public void renderIndirect(Token parent, Token child){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(child.getKey());
		String value = "$"+String.valueOf(child.getId());
		props.put(key.toString(), value);
	}
	
	public void renderImmediateMapKey(Token parent, Token child, int ct){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		key.append(".");
		key.append("k");
		String value = String.valueOf(child.getObject());
		props.put(key.toString(), value);
	}
	
	public void renderIndirectMapKey(Token parent, Token child, int ct){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		key.append(".");
		key.append("k");
		String value = "$"+String.valueOf(child.getId());
		props.put(key.toString(), value);
	}
	
	public void renderParentClass(Token parentToken){
		TokenType tt = parentToken.getTokenType();
		if(tt == TokenType.A){
			// put the length of the array as the third argument
			StringBuffer value = new StringBuffer();
			value.append(tt.name());
			value.append("|");
			value.append(bcnKey(parentToken.getObject().getClass()));
			value.append("|");
			value.append(parentToken.getArrayLength());
			
			props.put(String.valueOf(parentToken.getId()), value.toString());
			
		}else if(tt == TokenType.LI){
			// in third position, put the class name of the List type (if list not empty) or the token NONE
			StringBuffer buf = new StringBuffer();
			buf.append(tt.name());
			buf.append("|");
			buf.append(bcnKey(parentToken.getObject().getClass()));
			buf.append("|");
			int sz = ((List<?>)parentToken.getObject()).size();
			if(sz > 0) {
				Object item = ((List<?>)parentToken.getObject()).get(0);
				String itemClassName = bcnKey(item.getClass());
				buf.append(itemClassName);
			}else{
				buf.append("NONE");
			}
			props.put(String.valueOf(parentToken.getId()), buf.toString());
			
		}else if(tt == TokenType.M){
			// in third and fourth position put the key and value type respectively or NONE and NONE
			StringBuffer buf = new StringBuffer();
			buf.append(tt.name());
			buf.append("|");
			buf.append(bcnKey(parentToken.getObject().getClass()));
			buf.append("|");
			int sz = ((Map<?,?>)parentToken.getObject()).size();
			if(sz>0){
				Set<?> set = ((Map<?,?>)parentToken.getObject()).entrySet();
				Iterator<?> iter = set.iterator();
				Entry<?,?> entry = (Entry<?, ?>) iter.next();
				Object key= entry.getKey();
				Object value = entry.getValue();
				Class<?> keyClass = key.getClass();
				Class<?> valueClass = value.getClass();
				buf.append(bcnKey(keyClass));
				buf.append("|");
				buf.append(bcnKey(valueClass));
			}else{
				buf.append("NONE|NONE");
			}
			
			props.put(String.valueOf(parentToken.getId()), buf.toString());
			
		}else{
			//normal case, no third argument
			props.put(String.valueOf(parentToken.getId()), tt.name()+"|"+bcnKey(parentToken.getObject().getClass()));
		}
	}
	
	public void renderValue(Token parentToken){
		TokenType tt = parentToken.getTokenType();
		if(parentToken.getKey() != null && parentToken.getKey().equals("root")){
			props.put(String.valueOf(SerialIDGenerator.INSTANCE.increment()), tt.name()+"|"+String.valueOf(parentToken.getObject()));
		}else{
			props.put(String.valueOf(parentToken.getId()), tt.name()+"|"+String.valueOf(parentToken.getObject()));
		}
	}
	
	public void renderImmediateListItem(Token parent, Token child, int ct){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		String value = String.valueOf(child.getObject());
		props.put(key.toString(), value);
	}
	
	public void renderIndirectListItem(Token parent, Token child, int ct){
		StringBuffer key = new StringBuffer(String.valueOf(parent.getId()));
		key.append(".");
		key.append(ct);
		String value = "$"+String.valueOf(child.getId());
		props.put(key.toString(), value);
	}
	
	/**
	 * char [] array is stored as a String
	 * 
	 */
	public void renderCharArray(Token token){
		char [] array = (char[]) token.getObject();
		props.put(token.getId()+".string", new String(array));
	}
	
	public void renderByteArray(Token token){
		byte [] array = (byte[]) token.getObject();
		String str = Base64.encodeBytes(array);
		log.debug(token.getId()+".base64"+"="+str);
		props.put(token.getId()+".base64", str);
	}
	
	public void renderIntArray(Token token){
		   int[] data = (int[]) token.getObject();

	        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4);        
	        IntBuffer intBuffer = byteBuffer.asIntBuffer();
	        intBuffer.put(data);

	        byte[] array = byteBuffer.array();
			String str = Base64.encodeBytes(array);
			props.put(token.getId()+".base64", str);
	}
	
	public void renderShortArray(Token token){
		   short[] data = (short[]) token.getObject();

	        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 2);        
	        ShortBuffer intBuffer = byteBuffer.asShortBuffer();
	        intBuffer.put(data);

	        byte[] array = byteBuffer.array();
			String str = Base64.encodeBytes(array);
			props.put(token.getId()+".base64", str);
	}
	
	public void renderLongArray(Token token){
		   long[] data = (long[]) token.getObject();

	        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 8);        
	        LongBuffer longBuffer = byteBuffer.asLongBuffer();
	        longBuffer.put(data);

	        byte[] array = byteBuffer.array();
			String str = Base64.encodeBytes(array);
			props.put(token.getId()+".base64", str);
	}
	
	public void renderDoubleArray(Token token){
		   double[] data = (double[]) token.getObject();

	        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 8);        
	        DoubleBuffer dBuffer = byteBuffer.asDoubleBuffer();
	        dBuffer.put(data);

	        byte[] array = byteBuffer.array();
			String str = Base64.encodeBytes(array);
			props.put(token.getId()+".base64", str);
	}
	
	public void renderFloatArray(Token token){
		   float[] data = (float[]) token.getObject();

	        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4);        
	        FloatBuffer dBuffer = byteBuffer.asFloatBuffer();
	        dBuffer.put(data);

	        byte[] array = byteBuffer.array();
			String str = Base64.encodeBytes(array);
			props.put(token.getId()+".base64", str);
	}
	
	public void renderBooleanArray(Token token){
		 throw new UnsupportedOperationException("Nor ready for boolean array yet...");
	}

	public Properties getProps() {
		return props;
	}

	@Override
	public void renderClassMap() {
		packages.printToProperties(props);
		
	}

}
